面向对象有三个特点,一个个来说:

封装

私有变量

利用闭包实现对象的私有变量。


function Animal (age) {
  this.getAge = function () {
    return age
  }
}
var dog = new Animal(3)
console.log(dog.age) // undefined
console.log(dog.getAge()) // 3

碰过的一个有趣的问题:
实现一个book构造函数,有一个属性id,每次调用该值加1。
运用闭包和立刻执行函数。

let Book = (function () {
  let id = 1
  return function () {
    this.id = id++
  }
})()
let bok1 = new Book() 
let bok2 = new Book() 
let bok3 = new Book() 
console.log(bok3.id) // 3

共有方法

看下面的代码可以发现,getAge被重复创建了

var dog = new Animal(3)
var cat = new Animal(5)
console.log(dog.getAge === dog.getAge) // false

如果不想方法或者属性在每次new时新创建一份,可以将其设置在构造函数的原型prototype上。

Animal.prototype.feed = function () {
  console.log('feed')
}
console.log(dog.feed === cat.feed) // true

继承

听说继承有六种方法,假设让Dog继承Animal,无非就是for in 复制属性,修改原型链如dog.prototype = new Animal,直接Object.create,在Dog中使用Animal.call
然而我们记住最好的一种就够了,就是组合继承。

先试下这样写,利用call的继承:

function Animal (name) {
  this.name = name;
  this.say = function() {
    console.log(this.name)
  }
}
// *1
function Dog (color, name) {
  Animal.call(this, name)
  this.color = color
}


// *2
let wangcai = new Dog('blue', 'wangcai')
console.log(wangcai) // {color: 'blue', name: 'wangcai')
wangcai.say() // 'wangcal'

可是如果在*1处加上这样的代码

Animal.prototype.say2 = function() {
    console.log(this.name)
}

在*3处输入

wangcai.say2(),会报错提示不存在该方法,说明我们的继承是不完整的。dog没有继承原型链上的方法

我们需要在*2补上:

Dog.prototype = Object.create(Animal.prototype)

这时候不会报错了,补上Object.create的polyfill

function objectCreate (proto) {
  function F() {}
  F.prototype = proto;
  return new F();
}

然而还有一点小漏洞,当我们查看wangcai.constructor时,会发现指向的是Animal。因此我们需要修复一下:

Dog.prototype.constructor = Dog

补充一下new的模拟

function fNew (base) {
var o = {}
o.__proto__ = base.prototype
base.call(o)
return o
}

完整代码

function Animal (name) {
  this.name = name;
  this.say = function() {
    console.log(this.name)
  }
}
Animal.prototype.say2 = function () {
  console.log(this.name)
}
function Dog (color, name) {
  Animal.call(this, name)
  this.color = color
}
Dog.prototype = Object.create(Animal.prototype)
Dog.prototype.constructor = Dog
let wangcai = new Dog('blue', 'wangcai')

检测继承是否成功的代码:

console.log(wangcai instanceof Animal)
console.log(wangcai instanceof Dog)
console.log(wangcai.constructor === Dog)
console.log(wangcai.say2 === Animal.prototype.say2)
console.log(wangcai.__proto__ === Dog.prototype)
console.log(wangcai.__proto__.__proto__ === Animal.prototype)
console.log(wangcai.__proto__.__proto__.__proto__ === Object.prototype)
console.log(wangcai.constructor === Dog)

使用Object.create()和修复Dog.prototype.constructor = Dog是不是挺多余的?es6提供了这么一个函数Object.setPrototypeOf.

因此我们可以使用

Object.setPrototypeOf(Dog.prototype, Animal.prototype)

替换刚刚提到的两行代码

可以了解到Object.setPrototypeOf(A,B)相当于令A.__proto__ = B。

多态

一个函数可以应用于不同的对象。并且根据this的不同,函数调用的结果也不同

function test() {
  alert([this.a, this.b]);
}
 
test.call({a: 10, b: 20}); // 10, 20
test.call({a: 100, b: 200}); // 100, 200
 
var a = 1;
var b = 2;
 
test(); // 1, 2

或是在函数中检测arguments的数量和类型来实现多态

function add (a, b) {
  if (arguments.length  === 2) {
    return a + b
  } else {
    return a + 1
  }
}
console.log(add(1,4))
console.log(add(1))

END


皮小蛋
8k 声望12.8k 粉丝

积跬步,至千里。